package com.demo.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Desc: 占位符替换, 占位符表示为：{@code {placeholder}}；
 * <p>
 * 示例：替换如下${xxx}占位符中的内容
 * <pre>"名字:${name},年龄:${age},学校:${school}"</pre>
 *
 * @author wangfengchen
 */
@Slf4j
public class TemplateReplaceUtil {

  private static final Pattern PATTERN = Pattern.compile("\\$\\{(.*?)\\}");


  public static String replaceWithMap(String sourceString, Map<String, Object> param) throws SpelEvaluationException {
    try {
      if ((StringUtils.isEmpty(sourceString)) || param == null) {
        return sourceString;
      }
      Expression expression = new SpelExpressionParser().parseExpression(sourceString, new ParserContext() {
        @Override
        public boolean isTemplate() {
          return true;
        }

        @Override
        public String getExpressionPrefix() {
          return "${";
        }

        @Override
        public String getExpressionSuffix() {
          return "}";
        }
      });

      StandardEvaluationContext context = new StandardEvaluationContext(param);
      context.addPropertyAccessor(new MapAccessor());
      return String.valueOf(expression.getValue(context));
    } catch (ParseException | EvaluationException e) {
      log.error("替换模版参数出错：", e);
      throw new RuntimeException(e);
    }
  }

  /**
   * 替换字符串占位符, 字符串中使用{key}表示占位符
   *
   * @param sourceString 需要匹配的字符串，示例："名字:{name},年龄:{age},学校:{school}";
   * @param param        参数集,Map类型
   * @return
   */
  public static String replaceWithMapRegula(String sourceString, Map<String, Object> param) {
    if ((StringUtils.isEmpty(sourceString)) || param == null) {
      return sourceString;
    }

    String targetString = sourceString;
    Matcher matcher = PATTERN.matcher(sourceString);
    while (matcher.find()) {
      try {
        String key = matcher.group();
        String keyclone = key.substring(2, key.length() - 1).trim();
        Object value = param.get(keyclone);
        if (value != null) {
          targetString = targetString.replace(key, value.toString());
        }
      } catch (Exception e) {
        throw new RuntimeException("String formatter failed", e);
      }
    }
    return targetString;
  }

  /**
   * 替换字符串占位符, 字符串中使用{key}表示占位符
   * <p>
   * 利用反射 自动获取对象属性值 (必须有get方法)
   *
   * @param sourceString 需要匹配的字符串
   * @param param        参数集
   * @return
   */
  public static String replaceWithObject(String sourceString, Object param) {
    if (StringUtils.isEmpty(sourceString) || ObjectUtils.isEmpty(param)) {
      return sourceString;
    }

    String targetString = sourceString;

    PropertyDescriptor pd;
    Method getMethod;

    // 匹配{}中间的内容 包括括号
    Matcher matcher = PATTERN.matcher(sourceString);
    while (matcher.find()) {
      String key = matcher.group();
      String holderName = key.substring(2, key.length() - 1).trim();
      try {
        pd = new PropertyDescriptor(holderName, param.getClass());
        // 获得get方法
        getMethod = pd.getReadMethod();
        Object value = getMethod.invoke(param);
        if (value != null) {
          targetString = targetString.replace(key, value.toString());
        }
      } catch (Exception e) {
        throw new RuntimeException("String formatter failed", e);
      }
    }
    return targetString;
  }

  /**
   * 查找String中的占位符keys；<br/> 示例： "名字:{name},年龄:{age},学校:{school}"， 则返回：Set[name,age,school]
   * <p>
   * pattern示例：
   * <pre> {@code
   *  // 尖括号：<placeHolder> 表示为占位符
   *  Pattern pattern = Pattern.compile("\\<(.*?)\\>");
   *  // 大括号：{placeHolder} 表示为占位符， 上面的示例中就使用{}作为占位符
   *  Pattern pattern = Pattern.compile("\\{(.*?)\\}");
   * }
   * </pre>
   *
   * @param sourceString
   * @param pattern
   * @return
   */
  public static Set<String> findPlaceHolderKeys(String sourceString, Pattern pattern) {

    Set<String> placeHolderSet = new HashSet<>();

    if (StringUtils.isEmpty(sourceString) || ObjectUtils.isEmpty(pattern)) {
      return placeHolderSet;
    }

    String targetString = sourceString;
    Matcher matcher = pattern.matcher(sourceString);
    while (matcher.find()) {
      String key = matcher.group();
      String placeHolder = key.substring(2, key.length() - 1).trim();
      placeHolderSet.add(placeHolder);
    }

    return placeHolderSet;
  }

  /**
   * 判断替换完的字符串是否还存在{}
   *
   * @param sorce
   * @return
   */
  public static boolean find(String sorce) {
    Matcher matcher = PATTERN.matcher(sorce);
    return (matcher.find());
  }
}
